/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.appspot.dbfacade;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Transaction;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author Arif
 */
public class JsonController extends HttpServlet {

    public static final String URL_PATTERN = "/json/";

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        PrintWriter out = response.getWriter();
        List<ResourceParam> params = getParams(request);
        List<Map<String, Object>> responseKey = createResource(params, request);
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(Object.class, new NaturalDeserializer());
        Gson gson = gsonBuilder.create();
        try {
            out.print(gson.toJson(responseKey));
        } finally {
            out.close();
        }
    }

    /**
     * Handles the HTTP <code>GET</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        PrintWriter out = response.getWriter();
        List<ResourceParam> params = getParams(request);
        List<Map<String, Object>> result = fetchQuery(params);
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(Object.class, new NaturalDeserializer());
        Gson gson = gsonBuilder.create();
        try {
            out.print(gson.toJson(result));
        } finally {
            out.close();
        }
    }

    @Override
    protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        List<ResourceParam> params = getParams(request);
        List<Map<String, Object>> result = deleteQuery(params);
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(Object.class, new NaturalDeserializer());
        Gson gson = gsonBuilder.create();
        try {
            out.print(gson.toJson(result));
        } finally {
            out.close();
        }
    }

    @Override
    protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        List<ResourceParam> params = getParams(request);
        List<Map<String, Object>> responseKey = updateResource(params, request);
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(Object.class, new NaturalDeserializer());
        Gson gson = gsonBuilder.create();
        try {
            out.print(gson.toJson(responseKey));
        } finally {
            out.close();
        }
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=UTF-8");
        resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
        resp.addHeader("Cache-Control", "post-check=0, pre-check=0");
        resp.setHeader("Pragma", "no-cache");
        super.service(req, resp);
    }

    private List<Map<String, Object>> fetchQuery(List<ResourceParam> resourceParams) {
        List<Map<String, Object>> result = new ArrayList<>();
        DatastoreService service = DatastoreServiceFactory.getDatastoreService();
        for (ResourceParam resourceParam : resourceParams) {
            Query query = new Query(resourceParam.getResource());
            if (resourceParam.getId() != null) {
                // since we have key then we search based on key
                Key key = KeyFactory.createKey(resourceParam.getResource(), resourceParam.getId());
                try {
                    Entity entity = service.get(key);
                    Map<String, Object> properties = new HashMap<>();
                    properties.put(Entity.KEY_RESERVED_PROPERTY, entity.getKey().getId());
                    properties.putAll(entity.getProperties());
                    result.add(properties);
                } catch (EntityNotFoundException ex) {
                    Logger.getLogger(JsonController.class.getName()).log(Level.SEVERE, null, ex);
                }
            } else {
                Iterator<Entity> entities = service.prepare(query).asIterator();
                while (entities.hasNext()) {
                    Entity entity = entities.next();
                    Map<String, Object> properties = new HashMap<>();
                    properties.put(Entity.KEY_RESERVED_PROPERTY, entity.getKey().getId());
                    properties.putAll(entity.getProperties());
                    result.add(properties);
                }
            }

        }
        return result;
    }

    private List<ResourceParam> getParams(HttpServletRequest request) {
        String[] params = request.getRequestURI().substring(request.getContextPath().length() + URL_PATTERN.length()).split("/");
        int totalParameter = (int) Math.ceil(params.length / 2f);
        List<ResourceParam> list = new ArrayList<>();
        for (int i = 0; i < totalParameter; i++) {
            int j = i * 2;
            try {
                ResourceParam param = new ResourceParam(params[j], params.length > j + 1 ? Long.parseLong(params[j + 1]) : null);
                list.add(param);
            } catch (Exception ex) {
                Logger.getLogger(JsonController.class.getName()).log(Level.SEVERE, null, ex);
                break;
            }
        }
        return list;
    }

    private List<Map<String, Object>> createResource(List<ResourceParam> params, HttpServletRequest request) throws IOException {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(Object.class, new NaturalDeserializer());
        Gson gson = gsonBuilder.create();
        Object result = gson.fromJson(request.getReader(), Object.class);
        List<Entity> resource = new ArrayList<>();
        DatastoreService service = DatastoreServiceFactory.getDatastoreService();
        Transaction transaction = service.beginTransaction();
        if (result instanceof List) {
            for (Map map : (List<Map>) result) {
                resource.add(createEntity(map, params.get(0).getResource(), null, service));
            }
        } else {
            resource.add(createEntity((Map<String, Object>) result, params.get(0).getResource(), null, service));
        }
        transaction.commit();
        List<Map<String, Object>> keys = new ArrayList<>();
        for (Entity entity : resource) {
            Map<String, Object> response = new HashMap<>();
            response.put(Entity.KEY_RESERVED_PROPERTY, entity.getKey().getId());
            response.putAll(entity.getProperties());
            keys.add(response);
        }
        return keys;
    }

    private Entity createEntity(Map<String, Object> map, String resource, Key parent, DatastoreService service) {
        Entity entity = parent != null ? new Entity(resource, parent) : new Entity(resource);
        Map<String, List<Map>> childrens = new HashMap<>();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (value instanceof ArrayList) {
                childrens.put(key, (List) value);
            } else {
                entity.setProperty(key, value);
            }
        }
        Key key = service.put(entity);
        for (Map.Entry<String, List<Map>> entry : childrens.entrySet()) {
            String _childKey = entry.getKey();
            List<Map> list = entry.getValue();
            for (Map _map : list) {
                createEntity(_map, _childKey, key, service);
            }
        }
        return entity;
    }

    private List<Map<String, Object>> deleteQuery(List<ResourceParam> resourceParams) {
        List<Map<String, Object>> result = new ArrayList<>();
        DatastoreService service = DatastoreServiceFactory.getDatastoreService();
        for (ResourceParam resourceParam : resourceParams) {

            if (resourceParam.getId() != null) {

                Key key = KeyFactory.createKey(resourceParam.getResource(), resourceParam.getId());
                service.delete(key);
                Map<String, Object> _result = new HashMap<>();
                _result.put(Entity.KEY_RESERVED_PROPERTY, key.getId());
                result.add(_result);
            } else {
                Query query = new Query(resourceParam.getResource());
                Iterator<Entity> entities = service.prepare(query).asIterator();
                while (entities.hasNext()) {
                    Entity entity = entities.next();
                    service.delete(entity.getKey());
                    Map<String, Object> _result = new HashMap<>();
                    _result.put(Entity.KEY_RESERVED_PROPERTY, entity.getKey().getId());
                    result.add(_result);
                }
            }
        }
        return result;
    }

    private List<Map<String, Object>> updateResource(List<ResourceParam> params, HttpServletRequest request) throws IOException {
        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.registerTypeAdapter(Object.class, new NaturalDeserializer());
        Gson gson = gsonBuilder.create();
        Object result = gson.fromJson(request.getReader(), Object.class);
        List<Entity> resource = new ArrayList<>();
        DatastoreService service = DatastoreServiceFactory.getDatastoreService();
        Transaction transaction = service.beginTransaction();
        if (result instanceof List) {
            for (Map map : (List<Map>) result) {
                resource.add(updateEntity(map, params.get(0).getResource(), service));
            }
        } else {
            resource.add(updateEntity((Map<String, Object>) result, params.get(0).getResource(), service));
        }
        transaction.commit();
        List<Map<String, Object>> keys = new ArrayList<>();
        for (Entity entity : resource) {
            Map<String, Object> response = new HashMap<>();
            response.put(Entity.KEY_RESERVED_PROPERTY, entity.getKey().getId());
            response.putAll(entity.getProperties());
            keys.add(response);
        }
        return keys;
    }

    private Entity updateEntity(Map<String,Object> map, String resource, DatastoreService service)  {
        try {
            Key entityKey = KeyFactory.createKey(resource, Math.round((Double)map.get(Entity.KEY_RESERVED_PROPERTY)));
            map.remove(Entity.KEY_RESERVED_PROPERTY);
            Entity entity = service.get(entityKey);
            Map<String, List<Map>> childrens = new HashMap<>();
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                if (value instanceof ArrayList) {
                    childrens.put(key, (List) value);
                } else {
                    entity.setProperty(key, value);
                }
            }
            for (Map.Entry<String, List<Map>> entry : childrens.entrySet()) {
                String _childKey = entry.getKey();
                List<Map> list = entry.getValue();
                for (Map _map : list) {
                    updateEntity(_map, _childKey, service);
                }
            }
            return entity;
        } catch (EntityNotFoundException ex) {
            Logger.getLogger(JsonController.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
        
    }
}
